﻿///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Область СлужебныйПрограммныйИнтерфейс

////////////////////////////////////////////////////////////////////////////////
// Обработчики событий подсистем конфигурации.

// См. ОбщегоНазначенияПереопределяемый.ПриДобавленииПараметровРаботыКлиентаПриЗапуске.
Процедура ПриДобавленииПараметровРаботыКлиентаПриЗапуске(Параметры) Экспорт
	
	Если Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
		Возврат;
	КонецЕсли;
	
	ИспользоватьНапоминания = ПолучитьНастройкиНапоминаний().ИспользоватьНапоминания;
	СписокТекущихНапоминаний = ?(ИспользоватьНапоминания,
		СписокТекущихНапоминанийПользователя(), Новый Массив);
	
	Результат = Новый Структура;
	Результат.Вставить("ИспользоватьНапоминания",  ИспользоватьНапоминания);
	Результат.Вставить("СписокТекущихНапоминаний", СписокТекущихНапоминаний);
	
	Параметры.Вставить("НастройкиНапоминаний", Новый ФиксированнаяСтруктура(Результат));
	
КонецПроцедуры 

// См. ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений
Процедура ПриДобавленииСерверныхОповещений(Оповещения) Экспорт
	
	Если Не ПолучитьФункциональнуюОпцию("ИспользоватьНапоминанияПользователя") Тогда
		Возврат;
	КонецЕсли;
	
	Оповещение = СерверныеОповещения.НовоеСерверноеОповещение(
		НапоминанияПользователяКлиентСервер.ИмяСерверногоОповещения());
	
	Оповещение.ИмяМодуляОтправки  = "НапоминанияПользователяСлужебный";
	Оповещение.ИмяМодуляПолучения = "НапоминанияПользователяКлиент";
	
	Оповещения.Вставить(Оповещение.Имя, Оповещение);
	
КонецПроцедуры

// См. СтандартныеПодсистемыСервер.ПриОтправкеСерверногоОповещения
Процедура ПриОтправкеСерверногоОповещения(ИмяОповещения, ВариантыПараметров) Экспорт
	
	// Добавление оповещений выполняется при записи регистра сведений НапоминанияПользователя.
	// Периодическое оповещение требуется для гарантии доставки добавленных оповещений и
	// для актуализации списка напоминаний всех пользователей.
	
	АктуализироватьСписокНапоминаний(Ложь);
	
КонецПроцедуры

// См. ОбщегоНазначенияПереопределяемый.ПриДобавленииПереименованийОбъектовМетаданных.
Процедура ПриДобавленииПереименованийОбъектовМетаданных(Итог) Экспорт
	
	Библиотека = "СтандартныеПодсистемы";
	
	СтароеИмя = "Роль.ИспользованиеНапоминаний";
	НовоеИмя  = "Роль.ДобавлениеИзменениеНапоминаний";
	ОбщегоНазначения.ДобавитьПереименование(Итог, "2.3.3.11", СтароеИмя, НовоеИмя, Библиотека);
	
КонецПроцедуры

// См. УправлениеДоступомПереопределяемый.ПриЗаполненииСписковСОграничениемДоступа.
Процедура ПриЗаполненииСписковСОграничениемДоступа(Списки) Экспорт
	
	Списки.Вставить(Метаданные.РегистрыСведений.НапоминанияПользователя, Истина);
	
КонецПроцедуры

// Изменить текст напоминаний по предмету.
// 
// Параметры:
//  Предмет - ЛюбаяСсылка - предмет напоминания;
//  Идентификатор - Строка - уточняет предмет напоминания, например, "ДеньРождения".
//  НовыйТекст - Строка - текст напоминания;
//
Процедура ИзменитьТекстНапоминанийПоПредмету(Предмет, Идентификатор, НовыйТекст) Экспорт
	
	Запрос = Новый Запрос;
	Запрос.Текст =
		"ВЫБРАТЬ
		|	НапоминанияПользователя.Пользователь,
		|	НапоминанияПользователя.ВремяСобытия,
		|	НапоминанияПользователя.Источник
		|ИЗ
		|	РегистрСведений.НапоминанияПользователя КАК НапоминанияПользователя
		|ГДЕ
		|	НапоминанияПользователя.Идентификатор = &Идентификатор
		|	И НапоминанияПользователя.Источник = &Источник";
	
	Запрос.УстановитьПараметр("Идентификатор", Идентификатор);
	Запрос.УстановитьПараметр("Источник", Предмет);
	
	РезультатЗапроса = Запрос.Выполнить();
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		МенеджерЗаписи = РегистрыСведений.НапоминанияПользователя.СоздатьМенеджерЗаписи();
		ЗаполнитьЗначенияСвойств(МенеджерЗаписи, ВыборкаДетальныеЗаписи);
		МенеджерЗаписи.Прочитать();
		МенеджерЗаписи.Описание = НовыйТекст;
		МенеджерЗаписи.Записать();
	КонецЦикла;
	
КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

Функция НастройкиПодсистемы() Экспорт
	Настройки = Новый Структура;
	Настройки.Вставить("Расписания", ПолучитьСтандартныеРасписанияДляНапоминания());
	Настройки.Вставить("СтандартныеИнтервалы", СтандартныеИнтервалыОповещения());
	НапоминанияПользователяПереопределяемый.ПриОпределенииНастроек(Настройки);
	
	Возврат Настройки;
КонецФункции

Функция СтандартныеИнтервалыОповещения()
	
	Результат = Новый Массив;
	Результат.Добавить(НСтр("ru = '5 минут'"));
	Результат.Добавить(НСтр("ru = '10 минут'"));
	Результат.Добавить(НСтр("ru = '15 минут'"));
	Результат.Добавить(НСтр("ru = '30 минут'"));
	Результат.Добавить(НСтр("ru = '1 час'"));
	Результат.Добавить(НСтр("ru = '2 часа'"));
	Результат.Добавить(НСтр("ru = '4 часа'"));
	Результат.Добавить(НСтр("ru = '8 часов'"));
	Результат.Добавить(НСтр("ru = '1 день'"));
	Результат.Добавить(НСтр("ru = '2 дня'"));
	Результат.Добавить(НСтр("ru = '3 дня'"));
	Результат.Добавить(НСтр("ru = '1 неделю'"));
	Результат.Добавить(НСтр("ru = '2 недели'"));
	
	Возврат Результат;
	
КонецФункции

// Возвращает стандартные расписания для периодических напоминаний.
Функция ПолучитьСтандартныеРасписанияДляНапоминания()
	
	Результат = Новый Соответствие;
		
	// по понедельникам в 9:00
	Расписание = Новый РасписаниеРегламентногоЗадания;
	Расписание.ПериодПовтораДней = 1;
	Расписание.ПериодНедель = 1;
	Расписание.ВремяНачала = '00010101090000';
	ДниНедели = Новый Массив;
	ДниНедели.Добавить(1);
	Расписание.ДниНедели = ДниНедели;
	Результат.Вставить(НСтр("ru = 'по понедельникам, в 9:00'"), Расписание);
	
	// по пятницам в 15:00
	Расписание = Новый РасписаниеРегламентногоЗадания;
	Расписание.ПериодПовтораДней = 1;
	Расписание.ПериодНедель = 1;
	Расписание.ВремяНачала = '00010101150000';
	ДниНедели = Новый Массив;
	ДниНедели.Добавить(5);
	Расписание.ДниНедели = ДниНедели;
	Результат.Вставить(НСтр("ru = 'по пятницам, в 15:00'"), Расписание);
	
	// каждый день в 9:00
	Расписание = Новый РасписаниеРегламентногоЗадания;
	Расписание.ПериодПовтораДней = 1;
	Расписание.ПериодНедель = 1;
	Расписание.ВремяНачала = '00010101090000';
	Результат.Вставить(НСтр("ru = 'каждый день, в 9:00'"), Расписание);
	
	Возврат Результат;
	
КонецФункции

// Возвращает структуру настроек напоминаний пользователя.
Функция ПолучитьНастройкиНапоминаний()
	
	Результат = Новый Структура;
	Результат.Вставить("ИспользоватьНапоминания",
		ЕстьПравоИспользованияНапоминаний()
		И ПолучитьФункциональнуюОпцию("ИспользоватьНапоминанияПользователя"));
	
	Возврат Результат;
	
КонецФункции

// Проверяет наличие права изменения РС НапоминанияПользователя.
//
// Возвращаемое значение:
//  Булево - Истина, если право есть.
//
Функция ЕстьПравоИспользованияНапоминаний()
	Возврат ПравоДоступа("Изменение", Метаданные.РегистрыСведений.НапоминанияПользователя); 
КонецФункции

// Возвращает ближайшую дату по расписанию относительно даты, переданной в параметре.
//
// Параметры:
//  Расписание - РасписаниеРегламентногоЗадания - любое расписание.
//  ПредыдущаяДата - Дата - дата предыдущего события по расписанию.
//  ИскатьТолькоБудущиеДаты - Булево - Ложь, если требуется найти дату в прошлом.
//  
// Возвращаемое значение:
//   Дата - дата и время следующего события по расписанию.
//
Функция ПолучитьБлижайшуюДатуСобытияПоРасписанию(Расписание, ПредыдущаяДата = '000101010000', ИскатьТолькоБудущиеДаты = Истина) Экспорт

	Результат = Неопределено;
	ТекущаяДатаСеанса = ТекущаяДатаСеанса();
	
	ДатаОтсчета = ПредыдущаяДата;
	Если Не ЗначениеЗаполнено(ДатаОтсчета) Тогда
		ДатаОтсчета = ТекущаяДатаСеанса;
	КонецЕсли;
	Если ИскатьТолькоБудущиеДаты Тогда
		ДатаОтсчета = Макс(ДатаОтсчета, ТекущаяДатаСеанса);
	КонецЕсли;
	
	Календарь = ПолучитьКалендарьНаБудущее(365*4+1, ДатаОтсчета, Расписание.ДатаНачала, Расписание.ПериодПовтораДней, Расписание.ПериодНедель);
	
	ДниНедели = Расписание.ДниНедели;
	Если ДниНедели.Количество() = 0 Тогда
		ДниНедели = Новый Массив;
		Для День = 1 По 7 Цикл
			ДниНедели.Добавить(День);
		КонецЦикла;
	КонецЕсли;
	
	Месяцы = Расписание.Месяцы;
	Если Месяцы.Количество() = 0 Тогда
		Месяцы = Новый Массив;
		Для Месяц = 1 По 12 Цикл
			Месяцы.Добавить(Месяц);
		КонецЦикла;
	КонецЕсли;
	
	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	
	Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ Календарь ИЗ &Календарь КАК Календарь";
	Запрос.УстановитьПараметр("Календарь", Календарь);
	Запрос.Выполнить();
	
	Запрос.УстановитьПараметр("ДатаНачала",			Расписание.ДатаНачала);
	Запрос.УстановитьПараметр("ДатаКонца",			Расписание.ДатаКонца);
	Запрос.УстановитьПараметр("ДниНедели",			ДниНедели);
	Запрос.УстановитьПараметр("Месяцы",				Месяцы);
	Запрос.УстановитьПараметр("ДеньВМесяце",		Расписание.ДеньВМесяце);
	Запрос.УстановитьПараметр("ДеньНеделиВМесяце",	Расписание.ДеньНеделиВМесяце);
	Запрос.УстановитьПараметр("ПериодПовтораДней",	?(Расписание.ПериодПовтораДней = 0,1,Расписание.ПериодПовтораДней));
	Запрос.УстановитьПараметр("ПериодНедель",		?(Расписание.ПериодНедель = 0,1,Расписание.ПериодНедель));
	
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	Календарь.Дата,
	|	Календарь.НомерМесяца,
	|	Календарь.НомерДняНеделиВМесяце,
	|	Календарь.НомерДняНеделиСКонцаМесяца,
	|	Календарь.НомерДняВМесяце,
	|	Календарь.НомерДняВМесяцеСКонцаМесяца,
	|	Календарь.НомерДняВНеделе,
	|	Календарь.НомерДняВПериоде,
	|	Календарь.НомерНеделиВПериоде
	|ИЗ
	|	Календарь КАК Календарь
	|ГДЕ
	|	ВЫБОР
	|			КОГДА &ДатаНачала = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ Календарь.Дата >= &ДатаНачала
	|		КОНЕЦ
	|	И ВЫБОР
	|			КОГДА &ДатаКонца = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ Календарь.Дата <= &ДатаКонца
	|		КОНЕЦ
	|	И Календарь.НомерДняВНеделе В(&ДниНедели)
	|	И Календарь.НомерМесяца В(&Месяцы)
	|	И ВЫБОР
	|			КОГДА &ДеньВМесяце = 0
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ ВЫБОР
	|					КОГДА &ДеньВМесяце > 0
	|						ТОГДА Календарь.НомерДняВМесяце = &ДеньВМесяце
	|					ИНАЧЕ Календарь.НомерДняВМесяцеСКонцаМесяца = -&ДеньВМесяце
	|				КОНЕЦ
	|		КОНЕЦ
	|	И ВЫБОР
	|			КОГДА &ДеньНеделиВМесяце = 0
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ ВЫБОР
	|					КОГДА &ДеньНеделиВМесяце > 0
	|						ТОГДА Календарь.НомерДняНеделиВМесяце = &ДеньНеделиВМесяце
	|					ИНАЧЕ Календарь.НомерДняНеделиСКонцаМесяца = -&ДеньНеделиВМесяце
	|				КОНЕЦ
	|		КОНЕЦ
	|	И Календарь.НомерДняВПериоде = &ПериодПовтораДней
	|	И Календарь.НомерНеделиВПериоде = &ПериодНедель";
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	Если Выборка.Следующий() Тогда
		БлижайшаяДата = Выборка.Дата;
		ВремяОтсчета = '00010101';
		Если НачалоДня(БлижайшаяДата) = НачалоДня(ДатаОтсчета) Тогда
			ВремяОтсчета = ВремяОтсчета + (ДатаОтсчета-НачалоДня(ДатаОтсчета));
		КонецЕсли;
		
		БлижайшееВремя = ПолучитьБлижайшееВремяИзРасписания(Расписание, ВремяОтсчета);
		Если БлижайшееВремя <> Неопределено Тогда
			Результат = БлижайшаяДата + (БлижайшееВремя - '00010101');
		Иначе
			Если Выборка.Следующий() Тогда
				Время = ПолучитьБлижайшееВремяИзРасписания(Расписание);
				Результат = Выборка.Дата + (Время - '00010101');
			КонецЕсли;
		КонецЕсли;
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

Функция ПолучитьКалендарьНаБудущее(КоличествоДнейКалендаря, ДатаОтсчета, Знач ДатаНачалаПериодичности = Неопределено, Знач ПериодДней = 1, Знач ПериодНедель = 1) 
	
	Если ПериодНедель = 0 Тогда 
		ПериодНедель = 1;
	КонецЕсли;
	
	Если ПериодДней = 0 Тогда
		ПериодДней = 1;
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(ДатаНачалаПериодичности) Тогда
		ДатаНачалаПериодичности = ДатаОтсчета;
	КонецЕсли;
	
	Календарь = Новый ТаблицаЗначений;
	Календарь.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата",,,Новый КвалификаторыДаты()));
	Календарь.Колонки.Добавить("НомерМесяца", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(2,0,ДопустимыйЗнак.Неотрицательный)));
	Календарь.Колонки.Добавить("НомерДняНеделиВМесяце", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(1,0,ДопустимыйЗнак.Неотрицательный)));
	Календарь.Колонки.Добавить("НомерДняНеделиСКонцаМесяца", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(1,0,ДопустимыйЗнак.Неотрицательный)));
	Календарь.Колонки.Добавить("НомерДняВМесяце", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(2,0,ДопустимыйЗнак.Неотрицательный)));
	Календарь.Колонки.Добавить("НомерДняВМесяцеСКонцаМесяца", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(2,0,ДопустимыйЗнак.Неотрицательный)));
	Календарь.Колонки.Добавить("НомерДняВНеделе", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(2,0,ДопустимыйЗнак.Неотрицательный)));	
	Календарь.Колонки.Добавить("НомерДняВПериоде", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(3,0,ДопустимыйЗнак.Неотрицательный)));	
	Календарь.Колонки.Добавить("НомерНеделиВПериоде", Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(3,0,ДопустимыйЗнак.Неотрицательный)));
	
	Дата = НачалоДня(ДатаОтсчета);
	ДатаНачалаПериодичности = НачалоДня(ДатаНачалаПериодичности);
	НомерДняВПериоде = 0;
	НомерНеделиВПериоде = 0;
	
	Если ДатаНачалаПериодичности <= Дата Тогда
		КоличествоДней = (Дата - ДатаНачалаПериодичности)/60/60/24;
		НомерДняВПериоде = КоличествоДней - Цел(КоличествоДней/ПериодДней)*ПериодДней;
		
		КоличествоНедель = Цел(КоличествоДней / 7);
		НомерНеделиВПериоде = КоличествоНедель - Цел(КоличествоНедель/ПериодНедель)*ПериодНедель;
	КонецЕсли;
	
	Если НомерДняВПериоде = 0 Тогда 
		НомерДняВПериоде = ПериодДней;
	КонецЕсли;
	
	Если НомерНеделиВПериоде = 0 Тогда 
		НомерНеделиВПериоде = ПериодНедель;
	КонецЕсли;
	
	Для Счетчик = 0 По КоличествоДнейКалендаря - 1 Цикл
		
		Дата = НачалоДня(ДатаОтсчета) + Счетчик * 60*60*24;
		НоваяСтрока = Календарь.Добавить();
		НоваяСтрока.Дата = Дата;
		НоваяСтрока.НомерМесяца = Месяц(Дата);
		НоваяСтрока.НомерДняНеделиВМесяце = Цел((Дата - НачалоМесяца(Дата))/60/60/24/7) + 1;
		НоваяСтрока.НомерДняНеделиСКонцаМесяца = Цел((КонецМесяца(НачалоДня(Дата)) - Дата)/60/60/24/7) + 1;
		НоваяСтрока.НомерДняВМесяце = День(Дата);
		НоваяСтрока.НомерДняВМесяцеСКонцаМесяца = День(КонецМесяца(НачалоДня(Дата))) - День(Дата) + 1;
		НоваяСтрока.НомерДняВНеделе = ДеньНедели(Дата);
		
		Если ДатаНачалаПериодичности <= Дата Тогда
			НоваяСтрока.НомерДняВПериоде = НомерДняВПериоде;
			НоваяСтрока.НомерНеделиВПериоде = НомерНеделиВПериоде;
			
			НомерДняВПериоде = ?(НомерДняВПериоде+1 > ПериодДней, 1, НомерДняВПериоде+1);
			
			Если НоваяСтрока.НомерДняВНеделе = 1 Тогда
				НомерНеделиВПериоде = ?(НомерНеделиВПериоде+1 > ПериодНедель, 1, НомерНеделиВПериоде+1);
			КонецЕсли;
		КонецЕсли;
		
	КонецЦикла;
	
	Возврат Календарь;
	
КонецФункции

Функция ПолучитьБлижайшееВремяИзРасписания(Расписание, Знач ВремяОтсчета = '000101010000')
	
	Результат = Неопределено;
	
	СписокЗначений = Новый СписокЗначений;
	
	Если Расписание.ДетальныеРасписанияДня.Количество() = 0 Тогда
		СписокЗначений.Добавить(Расписание.ВремяНачала);
	Иначе
		Для Каждого РасписаниеДня Из Расписание.ДетальныеРасписанияДня Цикл
			СписокЗначений.Добавить(РасписаниеДня.ВремяНачала);
		КонецЦикла;
	КонецЕсли;
	
	СписокЗначений.СортироватьПоЗначению(НаправлениеСортировки.Возр);
	
	Для Каждого ВремяДня Из СписокЗначений Цикл
		Если ВремяОтсчета <= ВремяДня.Значение Тогда
			Результат = ВремяДня.Значение;
			Прервать;
		КонецЕсли;
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

// Параметры:
//  Расписание - РасписаниеРегламентногоЗадания
//  НачалоПериода - Дата
//  КонецПериода - Дата
// Возвращаемое значение:
//  Массив из Дата
// 
Функция ГрафикСобытия(Расписание, НачалоПериода, КонецПериода)

	Результат = Неопределено;
	
	ДатаОтсчета = НачалоПериода;
	
	Календарь = ПолучитьКалендарьНаБудущее((НачалоДня(КонецПериода) - НачалоДня(НачалоПериода)) / (60*60*24) + 1, 
		ДатаОтсчета, Расписание.ДатаНачала, Расписание.ПериодПовтораДней, Расписание.ПериодНедель);
	
	ДниНедели = Расписание.ДниНедели;
	Если ДниНедели.Количество() = 0 Тогда
		ДниНедели = Новый Массив;
		Для День = 1 По 7 Цикл
			ДниНедели.Добавить(День);
		КонецЦикла;
	КонецЕсли;
	
	Месяцы = Расписание.Месяцы;
	Если Месяцы.Количество() = 0 Тогда
		Месяцы = Новый Массив;
		Для Месяц = 1 По 12 Цикл
			Месяцы.Добавить(Месяц);
		КонецЦикла;
	КонецЕсли;
	
	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	
	Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ Календарь ИЗ &Календарь КАК Календарь";
	Запрос.УстановитьПараметр("Календарь", Календарь);
	Запрос.Выполнить();
	
	Запрос.УстановитьПараметр("ДатаНачала",			Расписание.ДатаНачала);
	Запрос.УстановитьПараметр("ДатаКонца",			Расписание.ДатаКонца);
	Запрос.УстановитьПараметр("ДниНедели",			ДниНедели);
	Запрос.УстановитьПараметр("Месяцы",				Месяцы);
	Запрос.УстановитьПараметр("ДеньВМесяце",		Расписание.ДеньВМесяце);
	Запрос.УстановитьПараметр("ДеньНеделиВМесяце",	Расписание.ДеньНеделиВМесяце);
	Запрос.УстановитьПараметр("ПериодПовтораДней",	?(Расписание.ПериодПовтораДней = 0,1,Расписание.ПериодПовтораДней));
	Запрос.УстановитьПараметр("ПериодНедель",		?(Расписание.ПериодНедель = 0,1,Расписание.ПериодНедель));
	
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	Календарь.Дата,
	|	Календарь.НомерМесяца,
	|	Календарь.НомерДняНеделиВМесяце,
	|	Календарь.НомерДняНеделиСКонцаМесяца,
	|	Календарь.НомерДняВМесяце,
	|	Календарь.НомерДняВМесяцеСКонцаМесяца,
	|	Календарь.НомерДняВНеделе,
	|	Календарь.НомерДняВПериоде,
	|	Календарь.НомерНеделиВПериоде
	|ИЗ
	|	Календарь КАК Календарь
	|ГДЕ
	|	ВЫБОР
	|			КОГДА &ДатаНачала = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ Календарь.Дата >= &ДатаНачала
	|		КОНЕЦ
	|	И ВЫБОР
	|			КОГДА &ДатаКонца = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ Календарь.Дата <= &ДатаКонца
	|		КОНЕЦ
	|	И Календарь.НомерДняВНеделе В(&ДниНедели)
	|	И Календарь.НомерМесяца В(&Месяцы)
	|	И ВЫБОР
	|			КОГДА &ДеньВМесяце = 0
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ ВЫБОР
	|					КОГДА &ДеньВМесяце > 0
	|						ТОГДА Календарь.НомерДняВМесяце = &ДеньВМесяце
	|					ИНАЧЕ Календарь.НомерДняВМесяцеСКонцаМесяца = -&ДеньВМесяце
	|				КОНЕЦ
	|		КОНЕЦ
	|	И ВЫБОР
	|			КОГДА &ДеньНеделиВМесяце = 0
	|				ТОГДА ИСТИНА
	|			ИНАЧЕ ВЫБОР
	|					КОГДА &ДеньНеделиВМесяце > 0
	|						ТОГДА Календарь.НомерДняНеделиВМесяце = &ДеньНеделиВМесяце
	|					ИНАЧЕ Календарь.НомерДняНеделиСКонцаМесяца = -&ДеньНеделиВМесяце
	|				КОНЕЦ
	|		КОНЕЦ
	|	И Календарь.НомерДняВПериоде = &ПериодПовтораДней
	|	И Календарь.НомерНеделиВПериоде = &ПериодНедель";
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	Результат = Новый Массив;
	
	Пока Выборка.Следующий() Цикл
		БлижайшаяДата = Выборка.Дата;
		ВремяОтсчета = '00010101';
		Если НачалоДня(БлижайшаяДата) = НачалоДня(ДатаОтсчета) Тогда
			ВремяОтсчета = ВремяОтсчета + (ДатаОтсчета-НачалоДня(ДатаОтсчета));
		КонецЕсли;
		
		ДатаИВремя = Неопределено;
		БлижайшееВремя = ПолучитьБлижайшееВремяИзРасписания(Расписание, ВремяОтсчета);
		Если БлижайшееВремя <> Неопределено Тогда
			ДатаИВремя = БлижайшаяДата + (БлижайшееВремя - '00010101');
		Иначе
			Если Выборка.Следующий() Тогда
				Время = ПолучитьБлижайшееВремяИзРасписания(Расписание);
				ДатаИВремя = Выборка.Дата + (Время - '00010101');
			КонецЕсли;
		КонецЕсли;
		
		Если ЗначениеЗаполнено(ДатаИВремя) И ДатаИВремя <= КонецПериода Тогда
			Результат.Добавить(ДатаИВремя);
		КонецЕсли;
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

// Проверяет изменения реквизитов предметов, на которые есть пользовательская подписка,
// изменяет срок напоминания в случае необходимости.
//
Процедура ОбновитьНапоминанияПоПредметам(Предметы) Экспорт
	
	УстановитьПривилегированныйРежим(Истина);
	
	ТаблицаРезультата = НапоминанияПоПредметам(Предметы);
	
	Для Каждого СтрокаТаблицы Из ТаблицаРезультата Цикл
		ДатаПредмета = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СтрокаТаблицы.Источник, СтрокаТаблицы.ИмяРеквизитаИсточника);
		Если (ДатаПредмета - СтрокаТаблицы.ИнтервалВремениНапоминания) <> СтрокаТаблицы.ВремяСобытия Тогда
			// @skip-check query-in-loop - запись данных набором с чтением в цикле.
			ОтключитьНапоминание(СтрокаТаблицы, Ложь);
			СтрокаТаблицы.СрокНапоминания = ДатаПредмета - СтрокаТаблицы.ИнтервалВремениНапоминания;
			СтрокаТаблицы.ВремяСобытия = ДатаПредмета;
			Если СтрокаТаблицы.Расписание.Получить() <> Неопределено Тогда
				СтрокаТаблицы.ПовторятьЕжегодно = Истина;
			КонецЕсли;
			
			ПараметрыНапоминания = ОбщегоНазначения.СтрокаТаблицыЗначенийВСтруктуру(СтрокаТаблицы);
			ПараметрыНапоминания.Расписание = СтрокаТаблицы.Расписание.Получить();
			// @skip-check query-in-loop - запись данных набором с чтением в цикле.
			Напоминание = СоздатьНапоминание(ПараметрыНапоминания);
			ПодключитьНапоминание(Напоминание);
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

// Параметры:
//  Предметы - Массив из ЛюбаяСсылка
// 
// Возвращаемое значение:
//  ТаблицаЗначений:
//   * Пользователь - СправочникСсылка.Пользователи
//   * ВремяСобытия - Дата
//   * Источник - ОпределяемыйТип.ПредметНапоминания
//   * СрокНапоминания - Дата
//   * Описание - Строка
//   * СпособУстановкиВремениНапоминания - ПеречислениеСсылка.СпособыУстановкиВремениНапоминания
//   * ИнтервалВремениНапоминания - Число
//   * ИмяРеквизитаИсточника - Строка
//   * Расписание - ХранилищеЗначения
//   * ПовторятьЕжегодно - Булево
//
Функция НапоминанияПоПредметам(Знач Предметы)
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	Напоминания.Пользователь,
	|	Напоминания.ВремяСобытия,
	|	Напоминания.Источник,
	|	Напоминания.СрокНапоминания,
	|	Напоминания.Описание,
	|	Напоминания.СпособУстановкиВремениНапоминания,
	|	Напоминания.ИнтервалВремениНапоминания,
	|	Напоминания.ИмяРеквизитаИсточника,
	|	Напоминания.Расписание,
	|	Напоминания.Идентификатор,
	|	ЛОЖЬ КАК ПовторятьЕжегодно
	|ИЗ
	|	РегистрСведений.НапоминанияПользователя КАК Напоминания
	|ГДЕ
	|	Напоминания.СпособУстановкиВремениНапоминания = ЗНАЧЕНИЕ(Перечисление.СпособыУстановкиВремениНапоминания.ОтносительноВремениПредмета)
	|	И Напоминания.Источник В(&Предметы)";
	
	Запрос.УстановитьПараметр("Предметы", Предметы);
	ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
	
	Возврат ТаблицаРезультата
	
КонецФункции


// Обработчик подписки на событие ПриЗаписи объекта, по поводу которого можно создавать напоминания.
Процедура ПроверитьИзмененияДатВПредметеПриЗаписи(Источник, Отказ) Экспорт
	
	Если Источник.ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	Если СтандартныеПодсистемыСервер.ЭтоИдентификаторОбъектаМетаданных(Источник) Тогда
		Возврат;
	КонецЕсли;
	
	Если ПолучитьФункциональнуюОпцию("ИспользоватьНапоминанияПользователя") Тогда
		ОбновитьНапоминанияПоПредметам(ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(Источник.Ссылка));
	КонецЕсли;
	
КонецПроцедуры

// Создает напоминание пользователя. Если по объекту уже есть напоминание, то сдвигает время напоминания на секунды вперед.
Процедура ПодключитьНапоминание(ПараметрыНапоминания, ОбновитьСрокНапоминания = Ложь, БезОповещенияКлиента = Ложь) Экспорт
	
	НаборЗаписей = РегистрыСведений.НапоминанияПользователя.СоздатьНаборЗаписей();
	Если БезОповещенияКлиента Тогда
		НаборЗаписей.ДополнительныеСвойства.Вставить("БезОповещенияКлиента");
	КонецЕсли;
	НаборЗаписей.Отбор.Пользователь.Установить(ПараметрыНапоминания.Пользователь);
	НаборЗаписей.Отбор.Источник.Установить(ПараметрыНапоминания.Источник);
	
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НапоминанияПользователя");
	ЭлементБлокировки.УстановитьЗначение("Пользователь", ПараметрыНапоминания.Пользователь);
	ЭлементБлокировки.УстановитьЗначение("Источник", ПараметрыНапоминания.Источник);
	
	Если ОбновитьСрокНапоминания Тогда
		НаборЗаписей.Отбор.ВремяСобытия.Установить(ПараметрыНапоминания.ВремяСобытия);
		ЭлементБлокировки.УстановитьЗначение("ВремяСобытия", ПараметрыНапоминания.ВремяСобытия);
	КонецЕсли;
	
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		НаборЗаписей.Прочитать();
		
		Если ОбновитьСрокНапоминания Тогда
			Для Каждого Запись Из НаборЗаписей Цикл
				ЗаполнитьЗначенияСвойств(Запись, ПараметрыНапоминания);
			КонецЦикла;
		Иначе
			Если НаборЗаписей.Количество() > 0 Тогда
				ЗанятоеВремя = НаборЗаписей.Выгрузить(,"ВремяСобытия").ВыгрузитьКолонку("ВремяСобытия");
				Пока ЗанятоеВремя.Найти(ПараметрыНапоминания.ВремяСобытия) <> Неопределено Цикл
					ПараметрыНапоминания.ВремяСобытия = ПараметрыНапоминания.ВремяСобытия + 1;
				КонецЦикла;
			КонецЕсли;
			НоваяЗапись = НаборЗаписей.Добавить();
			ЗаполнитьЗначенияСвойств(НоваяЗапись, ПараметрыНапоминания);
		КонецЕсли;
		
		Если НаборЗаписей.Количество() > 0 Тогда
			НаборЗаписей.Записать();
		КонецЕсли;
		
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
КонецПроцедуры

// Отключает напоминание, если оно есть. Если напоминание периодическое, подключает его на ближайшую дату по расписанию.
Процедура ОтключитьНапоминание(ПараметрыНапоминания, ПодключатьПоРасписанию = Истина, БезОповещенияКлиента = Ложь) Экспорт
	
	ТекстЗапроса = 
	"ВЫБРАТЬ
	|	НапоминанияПользователя.Пользователь КАК Пользователь,
	|	НапоминанияПользователя.ВремяСобытия КАК ВремяСобытия,
	|	НапоминанияПользователя.Источник КАК Источник,
	|	НапоминанияПользователя.СрокНапоминания КАК СрокНапоминания,
	|	НапоминанияПользователя.Описание КАК Описание,
	|	НапоминанияПользователя.СпособУстановкиВремениНапоминания КАК СпособУстановкиВремениНапоминания,
	|	НапоминанияПользователя.Расписание КАК Расписание,
	|	НапоминанияПользователя.ИнтервалВремениНапоминания КАК ИнтервалВремениНапоминания,
	|	НапоминанияПользователя.ИмяРеквизитаИсточника КАК ИмяРеквизитаИсточника,
	|	НапоминанияПользователя.Идентификатор КАК Идентификатор
	|ИЗ
	|	РегистрСведений.НапоминанияПользователя КАК НапоминанияПользователя
	|ГДЕ
	|	НапоминанияПользователя.Пользователь = &Пользователь
	|	И НапоминанияПользователя.ВремяСобытия = &ВремяСобытия
	|	И НапоминанияПользователя.Источник = &Источник";
	
	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.УстановитьПараметр("Пользователь", ПараметрыНапоминания.Пользователь);
	Запрос.УстановитьПараметр("ВремяСобытия", ПараметрыНапоминания.ВремяСобытия);
	Запрос.УстановитьПараметр("Источник", ПараметрыНапоминания.Источник);
	
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НапоминанияПользователя");
	ЭлементБлокировки.УстановитьЗначение("Пользователь", ПараметрыНапоминания.Пользователь);
	ЭлементБлокировки.УстановитьЗначение("Источник", ПараметрыНапоминания.Источник);
	ЭлементБлокировки.УстановитьЗначение("ВремяСобытия", ПараметрыНапоминания.ВремяСобытия);
	
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		
		РезультатЗапроса = Запрос.Выполнить().Выгрузить();
		Напоминание = Неопределено;
		Если РезультатЗапроса.Количество() > 0 Тогда
			Напоминание = РезультатЗапроса[0];
		КонецЕсли;
		
		// Удаляем существующую запись.
		НаборЗаписей = РегистрыСведений.НапоминанияПользователя.СоздатьНаборЗаписей();
		Если БезОповещенияКлиента Тогда
			НаборЗаписей.ДополнительныеСвойства.Вставить("БезОповещенияКлиента");
		КонецЕсли;
		НаборЗаписей.Отбор.Пользователь.Установить(ПараметрыНапоминания.Пользователь);
		НаборЗаписей.Отбор.Источник.Установить(ПараметрыНапоминания.Источник);
		НаборЗаписей.Отбор.ВремяСобытия.Установить(ПараметрыНапоминания.ВремяСобытия);
		НаборЗаписей.Записать();
		
		// Подключаем следующее напоминание по расписанию.
		СледующаяДатаПоРасписанию = Неопределено;
		ОпределенаСледующаяДатаПоРасписанию = Ложь;
		
		Если ПодключатьПоРасписанию И Напоминание <> Неопределено Тогда
			Расписание = Напоминание.Расписание.Получить();
			Если Расписание <> Неопределено Тогда
				Если Расписание.ПериодПовтораДней > 0 Тогда
					СледующаяДатаПоРасписанию = ПолучитьБлижайшуюДатуСобытияПоРасписанию(Расписание, ПараметрыНапоминания.ВремяСобытия + 1);
				КонецЕсли;
				ОпределенаСледующаяДатаПоРасписанию = СледующаяДатаПоРасписанию <> Неопределено;
			КонецЕсли;
			
			Если ОпределенаСледующаяДатаПоРасписанию Тогда
				Напоминание.ВремяСобытия = СледующаяДатаПоРасписанию;
				Напоминание.СрокНапоминания = Напоминание.ВремяСобытия - Напоминание.ИнтервалВремениНапоминания;
				ПодключитьНапоминание(Напоминание, БезОповещенияКлиента);
			КонецЕсли;
		КонецЕсли;
		
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
КонецПроцедуры

// Возвращаемое значение:
//  Структура:
//   * Пользователь - СправочникСсылка.Пользователи
//   * ВремяСобытия - Дата
//   * Источник - ОпределяемыйТип.ПредметНапоминания
//   * СрокНапоминания - Дата
//   * Описание - Строка
//   * СпособУстановкиВремениНапоминания - ПеречислениеСсылка.СпособыУстановкиВремениНапоминания
//   * ИнтервалВремениНапоминания - Число
//   * ИмяРеквизитаИсточника - Строка
//   * Расписание - ХранилищеЗначения - значение типа РасписаниеРегламентногоЗадания
//   * ПредставлениеИсточника - Строка
//   * Идентификатор - Строка
//
Функция ПодключитьПроизвольноеНапоминание(Текст, ВремяСобытия, ИнтервалДоСобытия = 0, Предмет = Неопределено, Идентификатор = Неопределено) Экспорт
	ПараметрыНапоминания = Новый Структура;
	ПараметрыНапоминания.Вставить("Описание", Текст);
	Если ТипЗнч(ВремяСобытия) = Тип("РасписаниеРегламентногоЗадания") Тогда
		ПараметрыНапоминания.Вставить("Расписание", ВремяСобытия);
	ИначеЕсли ТипЗнч(ВремяСобытия) = Тип("Строка") Тогда
		ПараметрыНапоминания.Вставить("ИмяРеквизитаИсточника", ВремяСобытия);
	Иначе
		ПараметрыНапоминания.Вставить("ВремяСобытия", ВремяСобытия);
	КонецЕсли;
	ПараметрыНапоминания.Вставить("ИнтервалВремениНапоминания", ИнтервалДоСобытия);
	ПараметрыНапоминания.Вставить("Источник", Предмет);
	ПараметрыНапоминания.Вставить("Идентификатор", Идентификатор);
	
	Напоминание = СоздатьНапоминание(ПараметрыНапоминания);
	ПодключитьНапоминание(Напоминание);
	
	Возврат Напоминание;
КонецФункции

Функция ПодключитьНапоминаниеДоВремениПредмета(Текст, Интервал, Предмет, ИмяРеквизита, ПовторятьЕжегодно = Ложь) Экспорт
	ПараметрыНапоминания = Новый Структура;
	ПараметрыНапоминания.Вставить("Описание", Текст);
	ПараметрыНапоминания.Вставить("Источник", Предмет);
	ПараметрыНапоминания.Вставить("ИмяРеквизитаИсточника", ИмяРеквизита);
	ПараметрыНапоминания.Вставить("ИнтервалВремениНапоминания", Интервал);
	ПараметрыНапоминания.Вставить("ПовторятьЕжегодно", ПовторятьЕжегодно);
	
	Напоминание = СоздатьНапоминание(ПараметрыНапоминания);
	ПодключитьНапоминание(Напоминание);
	
	Возврат Напоминание;
КонецФункции

// Возвращает структуру нового напоминания для последующего подключения.
Функция СоздатьНапоминание(ПараметрыНапоминания)
	
	Напоминание = НапоминанияПользователяКлиентСервер.ОписаниеНапоминания(ПараметрыНапоминания, Истина);
	
	Если Не ЗначениеЗаполнено(Напоминание.Пользователь) Тогда
		Напоминание.Пользователь = Пользователи.ТекущийПользователь();
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(Напоминание.СпособУстановкиВремениНапоминания) Тогда
		Если ЗначениеЗаполнено(Напоминание.Источник) И Не ПустаяСтрока(Напоминание.ИмяРеквизитаИсточника) Тогда
			Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ОтносительноВремениПредмета;
		ИначеЕсли Напоминание.Расписание <> Неопределено Тогда
			Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.Периодически;
		ИначеЕсли Не ЗначениеЗаполнено(Напоминание.ВремяСобытия) Тогда
			Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ОтносительноТекущегоВремени;
		Иначе
			Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ВУказанноеВремя;
		КонецЕсли;
	КонецЕсли;
	
	Если Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ОтносительноВремениПредмета Тогда
		Напоминание.ВремяСобытия = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Напоминание.Источник, Напоминание.ИмяРеквизитаИсточника);
		Напоминание.СрокНапоминания = Напоминание.ВремяСобытия - ?(ЗначениеЗаполнено(Напоминание.ВремяСобытия), Напоминание.ИнтервалВремениНапоминания, 0);
	ИначеЕсли Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ОтносительноТекущегоВремени Тогда
		Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ВУказанноеВремя;
		Напоминание.ВремяСобытия = ТекущаяДатаСеанса() + Напоминание.ИнтервалВремениНапоминания;
	ИначеЕсли Напоминание.СпособУстановкиВремениНапоминания = Перечисления.СпособыУстановкиВремениНапоминания.ВУказанноеВремя Тогда
		Напоминание.СрокНапоминания = Напоминание.ВремяСобытия - Напоминание.ИнтервалВремениНапоминания;
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(Напоминание.СрокНапоминания) Тогда
		Напоминание.СрокНапоминания = Напоминание.ВремяСобытия;
	КонецЕсли;
	
	Если Напоминание.ПовторятьЕжегодно Тогда
		Если ЗначениеЗаполнено(Напоминание.ВремяСобытия) Тогда
			Напоминание.Расписание = НапоминанияПользователяКлиентСервер.ЕжегодноеРасписание(Напоминание.ВремяСобытия);
		КонецЕсли;
	КонецЕсли;
	
	Если Напоминание.Расписание <> Неопределено Тогда
		Напоминание.ВремяСобытия = ПолучитьБлижайшуюДатуСобытияПоРасписанию(Напоминание.Расписание);
		Если Напоминание.ВремяСобытия = Неопределено Тогда
			Напоминание.ВремяСобытия = '00010101';
		КонецЕсли;
		Напоминание.СрокНапоминания = Напоминание.ВремяСобытия - Напоминание.ИнтервалВремениНапоминания;
	КонецЕсли;
	
	Напоминание.Расписание = Новый ХранилищеЗначения(Напоминание.Расписание, Новый СжатиеДанных(9));
	
	Возврат Напоминание;
	
КонецФункции

// Возвращаемое значение:
//  Структура:
//   * Добавленные - см. СписокТекущихНапоминанийПользователя
//   * Удаленные   - см. СписокТекущихНапоминанийПользователя
//
Функция НовыеИзмененныеНапоминания() Экспорт
	
	Возврат Новый Структура("Добавленные, Удаленные", Новый Массив, Новый Массив);
	
КонецФункции

// Прекращает просроченные периодические напоминания.
Процедура АктуализироватьСписокНапоминаний(ПриЗапуске)
	
	ТекстЗапроса =
	"ВЫБРАТЬ РАЗРЕШЕННЫЕ
	|	Напоминания.Пользователь КАК Пользователь,
	|	Напоминания.ВремяСобытия КАК ВремяСобытия,
	|	Напоминания.Источник КАК Источник,
	|	Напоминания.СрокНапоминания КАК СрокНапоминания,
	|	Напоминания.Описание КАК Описание,
	|	Напоминания.СпособУстановкиВремениНапоминания КАК СпособУстановкиВремениНапоминания,
	|	Напоминания.ИнтервалВремениНапоминания КАК ИнтервалВремениНапоминания,
	|	Напоминания.ИмяРеквизитаИсточника КАК ИмяРеквизитаИсточника,
	|	Напоминания.Расписание КАК Расписание,
	|	Напоминания.ПредставлениеИсточника КАК ПредставлениеИсточника,
	|	Напоминания.Идентификатор КАК Идентификатор
	|ИЗ
	|	РегистрСведений.НапоминанияПользователя КАК Напоминания
	|ГДЕ
	|	Напоминания.ВремяСобытия <= &ТекущаяДата
	|	И &ОтборПоПользователю";
	
	Запрос = Новый Запрос(ТекстЗапроса);
	
	Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса());
	Если ПриЗапуске Тогда
		Запрос.УстановитьПараметр("Пользователь", Пользователи.ТекущийПользователь());
		Запрос.Текст = СтрЗаменить(Запрос.Текст, "&ОтборПоПользователю",
			"Напоминания.Пользователь = &Пользователь");
	Иначе
		Запрос.Текст = СтрЗаменить(Запрос.Текст, "&ОтборПоПользователю", "ИСТИНА");
	КонецЕсли;
	
	УстановитьПривилегированныйРежим(Истина);
	СписокНапоминаний = Запрос.Выполнить().Выгрузить();
	УстановитьПривилегированныйРежим(Ложь);
	
	Для Каждого Напоминание Из СписокНапоминаний Цикл
		Расписание = Напоминание.Расписание.Получить();
		Если Расписание = Неопределено Тогда
			Продолжить;
		КонецЕсли;
		
		// @skip-check query-in-loop - нет обращения к данным ИБ в запросе.
		ГрафикСобытияНаГод = ГрафикСобытия(Расписание, Напоминание.ВремяСобытия, ДобавитьМесяц(Напоминание.ВремяСобытия, 12) - 1);
		// @skip-check query-in-loop - нет обращения к данным ИБ в запросе.
		ПодошедшееВремяСобытия = ГрафикСобытия(Расписание, Напоминание.ВремяСобытия + 1, ТекущаяДатаСеанса());
		
		// - подошло следующее время события по расписанию;
		Если ПодошедшееВремяСобытия.Количество() > 0
			// - указано "Время завершения" в расписании регламентного задания и оно просрочено;
			Или ЗначениеЗаполнено(Расписание.ВремяЗавершения) И ТекущаяДатаСеанса() > (Напоминание.ВремяСобытия + (Расписание.ВремяЗавершения - Расписание.ВремяНачала))
			// - напоминание ежегодное, но уже прошел месяц со времени события;
			Или ГрафикСобытияНаГод.Количество() = 1 И ТекущаяДатаСеанса() > ДобавитьМесяц(Напоминание.ВремяСобытия, 1)
			// - напоминание ежемесячное, но уже прошла неделя со времени события.
			Или ГрафикСобытияНаГод.Количество() = 12 И ТекущаяДатаСеанса() > Напоминание.ВремяСобытия + 60*60*24*7 Тогда
				НачатьТранзакцию();
				Попытка
					Если ПодошедшееВремяСобытия.Количество() > 0 Тогда
						// @skip-check query-in-loop - запись данных набором с чтением в цикле.
						ОтключитьНапоминание(Напоминание, Ложь, ПриЗапуске);
						
						ПараметрыНапоминания = ОбщегоНазначения.СтрокаТаблицыЗначенийВСтруктуру(Напоминание);
						ПараметрыНапоминания.Расписание = Напоминание.Расписание.Получить();
						// @skip-check query-in-loop - запись данных набором с чтением в цикле.
						Напоминание = СоздатьНапоминание(ПараметрыНапоминания);
						Напоминание.ВремяСобытия = ПодошедшееВремяСобытия[ПодошедшееВремяСобытия.ВГраница()];
						Напоминание.СрокНапоминания = Напоминание.ВремяСобытия - Напоминание.ИнтервалВремениНапоминания;
						
						ПодключитьНапоминание(Напоминание,, ПриЗапуске);
					КонецЕсли;
					// @skip-check query-in-loop - запись данных набором с чтением в цикле.
					ОтключитьНапоминание(Напоминание, Истина, ПриЗапуске);
					ЗафиксироватьТранзакцию();
				Исключение
					ОтменитьТранзакцию();
					ЗаписьЖурналаРегистрации(НСтр("ru = 'Напоминания пользователя'", ОбщегоНазначения.КодОсновногоЯзыка()),
						УровеньЖурналаРегистрации.Ошибка, Метаданные.РегистрыСведений.НапоминанияПользователя, , ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
				КонецПопытки;
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

// Выполняет запрос по напоминаниям для текущего пользователя на 30 минут вперед от текущего времени.
// Момент времени смещен от текущего для того, чтобы данные были актуальны в течение жизни кэша.
// При обработке результата выполнения функции необходимо учитывать эту особенность.
//
// Возвращаемое значение:
//  Массив из Структура:
//   * Пользователь - СправочникСсылка.Пользователи
//   * ВремяСобытия - Дата
//   * Источник - ОпределяемыйТип.ПредметНапоминания
//   * СрокНапоминания - Дата
//   * Описание - Строка
//   * ИндексКартинки - Число
//
Функция СписокТекущихНапоминанийПользователя() Экспорт
	
	АктуализироватьСписокНапоминаний(Истина);
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ РАЗРЕШЕННЫЕ
	|	Напоминания.Пользователь КАК Пользователь,
	|	Напоминания.ВремяСобытия КАК ВремяСобытия,
	|	Напоминания.Источник КАК Источник,
	|	Напоминания.СрокНапоминания КАК СрокНапоминания,
	|	Напоминания.Описание КАК Описание,
	|	2 КАК ИндексКартинки
	|ИЗ
	|	РегистрСведений.НапоминанияПользователя КАК Напоминания
	|ГДЕ
	|	Напоминания.СрокНапоминания <= &ГраницаСрокаНапоминания
	|	И Напоминания.Пользователь = &Пользователь
	|
	|УПОРЯДОЧИТЬ ПО
	|	ВремяСобытия";
	
	Запрос.УстановитьПараметр("ГраницаСрокаНапоминания",
		ТекущаяДатаСеанса() + ЗапасВремениНапоминанийДляКэша());
	
	Запрос.УстановитьПараметр("Пользователь", Пользователи.ТекущийПользователь());
	
	УстановитьПривилегированныйРежим(Истина);
	Результат = ОбщегоНазначения.ТаблицаЗначенийВМассив(Запрос.Выполнить().Выгрузить());
	
	Возврат Результат;
	
КонецФункции

Функция ЗапасВремениНапоминанийДляКэша() Экспорт
	
	Возврат 30*60;
	
КонецФункции

// См. НапоминанияПользователя.ПриЧтенииНаСервере
Процедура ПриСозданииНаСервере(Форма, ПараметрыРазмещения) Экспорт
	
	Если Не ПолучитьФункциональнуюОпцию("ИспользоватьНапоминанияПользователя") Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ПравоДоступа("Редактирование", Метаданные.РегистрыСведений.НапоминанияПользователя) Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(ПараметрыРазмещения.ИмяРеквизитаСДатойСобытия) Тогда
		Возврат;
	КонецЕсли;
	
	Объект = Неопределено;
	Для Каждого Реквизит Из Форма.ПолучитьРеквизиты() Цикл
		Если ТипЗнч(Форма[Реквизит.Имя]) = Тип("ДанныеФормыСтруктура") Тогда
			Объект = Форма[Реквизит.Имя];
			Прервать;
		КонецЕсли;
	КонецЦикла;
	
	Если Объект = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	ИмяРеквизитаСДатойСобытия = ПараметрыРазмещения.ИмяРеквизитаСДатойСобытия;
	
	Предмет = Объект.Ссылка;
	ОбъектМетаданных = Предмет.Метаданные();
	
	РеквизитСДатой = ОбъектМетаданных.Реквизиты.Найти(ИмяРеквизитаСДатойСобытия);
	Если РеквизитСДатой = Неопределено Тогда
		ЗначенияСвойств = Новый Структура("ИмяРеквизитаСДатойСобытия");
		ЗаполнитьЗначенияСвойств(ЗначенияСвойств, ОбъектМетаданных.СтандартныеРеквизиты);
		РеквизитСДатой = ЗначенияСвойств.ИмяРеквизитаСДатойСобытия;
	КонецЕсли;
	
	Если РеквизитСДатой = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	ВыводитьКороткиеИнтервалы = Истина;
	ОписаниеТипаДата = РеквизитСДатой.Тип; // ОписаниеТипов
	Если ОписаниеТипаДата.КвалификаторыДаты.ЧастиДаты = ЧастиДаты.Дата Тогда
		ВыводитьКороткиеИнтервалы = Ложь;
	КонецЕсли;
		
	ИмяПоляНастройкиНапоминания = НапоминанияПользователяКлиентСервер.ИмяПоляНастройкиНапоминания();
	ИмяГруппыЭлементов = "НастройкаНапоминания";
	ИмяПоляИнтервалВремениНапоминания = НапоминанияПользователяКлиентСервер.ИмяПоляИнтервалВремениНапоминания();
	ИмяПоляНапоминатьОСобытии = ИмяПоляНапоминатьОСобытии();
	
	НастройкиНапоминания = ПараметрыРазмещения();
	НастройкиНапоминания.Удалить("Группа");
	
	ЗаполнитьЗначенияСвойств(НастройкиНапоминания, ПараметрыРазмещения);
	
	НастройкиНапоминания.Вставить("Предмет", Предмет);
	
	Если Форма.Элементы.Найти(ИмяГруппыЭлементов) = Неопределено Тогда
		ДобавляемыеРеквизиты = Новый Массив;
		
		ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы(ИмяПоляНастройкиНапоминания, Новый ОписаниеТипов));
			
		ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы(ИмяПоляНапоминатьОСобытии,
			Новый ОписаниеТипов("Булево"), , НСтр("ru = 'Напомнить'"), Истина));
			
		ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы(ИмяПоляИнтервалВремениНапоминания,
			Новый ОписаниеТипов("Строка"), ,  НСтр("ru = 'Интервал времени напоминания'"), Истина));
		
		Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
		
		Группа = Форма.Элементы.Добавить(ИмяГруппыЭлементов, Тип("ГруппаФормы"), ПараметрыРазмещения.Группа);
		Группа.Вид = ВидГруппыФормы.ОбычнаяГруппа;
		Группа.ОтображатьЗаголовок = Ложь;
		Группа.Заголовок = НСтр("ru = 'Настройка напоминания'");
		Группа.Отображение = ОтображениеОбычнойГруппы.Нет;
		
		Если НастройкиНапоминания.ДобавлятьФлажок Тогда
			Флажок = Форма.Элементы.Добавить(ИмяПоляНапоминатьОСобытии, Тип("ПолеФормы"), Группа);
			Флажок.ПутьКДанным = ИмяПоляНапоминатьОСобытии;
			Флажок.Вид = ВидПоляФормы.ПолеФлажка;
			Флажок.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Право;
			Флажок.Заголовок = НСтр("ru = 'Напомнить:'");
		КонецЕсли;
		
		ПолеВвода = Форма.Элементы.Добавить(ИмяПоляИнтервалВремениНапоминания, Тип("ПолеФормы"), Группа);
		ПолеВвода.ПутьКДанным = ИмяПоляИнтервалВремениНапоминания;
		ПолеВвода.Вид = ВидПоляФормы.ПолеВвода;
		ПолеВвода.Подсказка = НСтр("ru = 'Время, за которое необходимо напомнить о событии'");
		Если НастройкиНапоминания.ДобавлятьФлажок Тогда
			ПолеВвода.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Нет;
		Иначе
			ПолеВвода.Заголовок = НСтр("ru = 'Напомнить'");
		КонецЕсли;
		ПолеВвода.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииНастройкиНапоминания");
		ПолеВвода.ОбновлениеТекстаРедактирования = ОбновлениеТекстаРедактирования.ПриИзмененииЗначения;
		ПолеВвода.КнопкаВыпадающегоСписка = Истина;
		ПолеВвода.Ширина = 12;
		ПолеВвода.РастягиватьПоГоризонтали = Ложь;
		
		НастройкиПодсистемы = НастройкиПодсистемы();
		ИнтервалыВремени = НастройкиПодсистемы.СтандартныеИнтервалы;
		
		ПолеВвода.СписокВыбора.Очистить();
		Если Не НастройкиНапоминания.ДобавлятьФлажок Тогда
			ПолеВвода.СписокВыбора.Добавить(НапоминанияПользователяКлиентСервер.ПредставлениеПеречисленияНеНапоминать());
		КонецЕсли;
		ПолеВвода.СписокВыбора.Добавить(НапоминанияПользователяКлиентСервер.ПредставлениеПеречисленияПриНаступлении());
		
		Для Каждого Интервал Из ИнтервалыВремени Цикл
			Если Не ВыводитьКороткиеИнтервалы И ИнтервалВремениИзСтроки(Интервал) < 24*60*60 Тогда
				Продолжить;
			КонецЕсли;

			ПолеВвода.СписокВыбора.Добавить(СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'за %1'"), Интервал));
		КонецЦикла;
		
		Если НастройкиНапоминания.ИнтервалВремениНапоминания = Неопределено Тогда
			НастройкиНапоминания.ИнтервалВремениНапоминания = 0;
		КонецЕсли;
		
		Если НастройкиНапоминания.ДобавлятьФлажок Тогда
			Если НастройкиНапоминания.ИнтервалВремениНапоминания = 0 Тогда
				Форма[ИмяПоляИнтервалВремениНапоминания] = НапоминанияПользователяКлиентСервер.ПредставлениеПеречисленияПриНаступлении();
			Иначе
				Форма[ИмяПоляИнтервалВремениНапоминания] = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'за %1'"), ПредставлениеВремени(НастройкиНапоминания.ИнтервалВремениНапоминания, , Ложь));
			КонецЕсли;
		Иначе
			Форма[ИмяПоляИнтервалВремениНапоминания] = НапоминанияПользователяКлиентСервер.ПредставлениеПеречисленияНеНапоминать();
		КонецЕсли;
		
		Форма[ИмяПоляНастройкиНапоминания] = НастройкиНапоминания;
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(Предмет) Тогда
		Возврат;
	КонецЕсли;
	
	ПрочитатьНастройкиНапоминанияПоПредмету(Форма, Предмет);
	
КонецПроцедуры

// См. НапоминанияПользователя.ПриЧтенииНаСервере
Процедура ПриЧтенииНаСервере(Форма, ТекущийОбъект) Экспорт
	
	Если Не ПолучитьФункциональнуюОпцию("ИспользоватьНапоминанияПользователя") Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ПравоДоступа("Редактирование", Метаданные.РегистрыСведений.НапоминанияПользователя) Тогда
		Возврат;
	КонецЕсли;
	
	НастройкиНапоминания = НапоминанияПользователяКлиентСервер.НастройкиНапоминанияВФорме(Форма);
	Если НастройкиНапоминания = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Предмет = ТекущийОбъект.Ссылка;
	ПрочитатьНастройкиНапоминанияПоПредмету(Форма, Предмет);
	
КонецПроцедуры

Процедура ПриЗаписиНаСервере(Форма, Отказ, ТекущийОбъект, ПараметрыЗаписи, Знач ТекстНапоминания) Экспорт
	
	Если Не ПолучитьФункциональнуюОпцию("ИспользоватьНапоминанияПользователя") Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ПравоДоступа("Редактирование", Метаданные.РегистрыСведений.НапоминанияПользователя) Тогда
		Возврат;
	КонецЕсли;
	
	НастройкиНапоминания = НапоминанияПользователяКлиентСервер.НастройкиНапоминанияВФорме(Форма);
	НапоминаниеВключено = Форма[ИмяПоляНапоминатьОСобытии()];
	Предмет = ТекущийОбъект.Ссылка;
	НастройкиНапоминания.Вставить("Предмет", Предмет);
	
	ИмяРеквизитаСДатойСобытия = НастройкиНапоминания.ИмяРеквизитаСДатойСобытия;
	ИнтервалВремениНапоминания = НастройкиНапоминания.ИнтервалВремениНапоминания;
	Текст = ТекстНапоминания;
	Если Не ЗначениеЗаполнено(Текст) Тогда
		Текст = ОбщегоНазначения.ПредметСтрокой(Предмет);
	КонецЕсли;
	
	Если НапоминаниеВключено Тогда
		ПереустановитьНапоминаниеПоПредмету(Предмет, ИмяРеквизитаСДатойСобытия, ИнтервалВремениНапоминания, Текст);
	Иначе
		УдалитьНапоминанияПоПредмету(Предмет, ИмяРеквизитаСДатойСобытия)
	КонецЕсли;
	
КонецПроцедуры

// См. НапоминанияПользователя.ПараметрыРазмещения
Функция ПараметрыРазмещения() Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Группа");
	Результат.Вставить("ИмяРеквизитаСДатойСобытия");
	Результат.Вставить("ИнтервалВремениНапоминания");
	Результат.Вставить("ДобавлятьФлажок", Ложь);
	
	Возврат Результат;
	
КонецФункции

// См. НапоминанияПользователяКлиентСервер.ПредставлениеВремени
Функция ПредставлениеВремени(Знач Время, ПолноеПредставление = Истина, ВыводитьСекунды = Истина)
		
	Возврат НапоминанияПользователяКлиентСервер.ПредставлениеВремени(Время, ПолноеПредставление, ВыводитьСекунды);
	
КонецФункции

Процедура УдалитьНапоминанияПоПредмету(Предмет, ИмяРеквизита)
	
	НапоминанияПоПредмету = НапоминанияПользователя.НайтиНапоминания(Предмет);
	Для Каждого Напоминание Из НапоминанияПоПредмету Цикл
		Если НРег(Напоминание.ИмяРеквизитаИсточника) = НРег(ИмяРеквизита) Тогда
			// @skip-check query-in-loop - запись данных набором с чтением в цикле.
			ОтключитьНапоминание(Напоминание, Ложь);
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

Процедура ПереустановитьНапоминаниеПоПредмету(Предмет, ИмяРеквизита, ИнтервалВремениНапоминания, Текст)

	УдалитьНапоминанияПоПредмету(Предмет, ИмяРеквизита);
	ПодключитьПроизвольноеНапоминание(Текст, ИмяРеквизита, ИнтервалВремениНапоминания, Предмет);
	
КонецПроцедуры

// См. НапоминанияПользователяКлиентСервер.ИнтервалВремениИзСтроки
Функция ИнтервалВремениИзСтроки(Знач СтрокаСоВременем) Экспорт
	
	Возврат НапоминанияПользователяКлиентСервер.ИнтервалВремениИзСтроки(СтрокаСоВременем);
	
КонецФункции

Функция ИмяПоляНапоминатьОСобытии() Экспорт
	
	Возврат НапоминанияПользователяКлиентСервер.ИмяПоляНапоминатьОСобытии();
	
КонецФункции

Процедура ПрочитатьНастройкиНапоминанияПоПредмету(Форма, Предмет)
	
	НастройкиНапоминания = НапоминанияПользователяКлиентСервер.НастройкиНапоминанияВФорме(Форма);
	Если НастройкиНапоминания = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	НастройкиНапоминания.Вставить("Предмет", Предмет);
	
	НапоминанияПоПредмету = НапоминанияПользователя.НайтиНапоминания(Предмет);
	Для Каждого Напоминание Из НапоминанияПоПредмету Цикл
		Если НРег(Напоминание.ИмяРеквизитаИсточника) = НРег(НастройкиНапоминания.ИмяРеквизитаСДатойСобытия) Тогда
			ИмяПоляНапоминатьОСобытии = ИмяПоляНапоминатьОСобытии();
			ИмяПоляИнтервалВремениНапоминания = НапоминанияПользователяКлиентСервер.ИмяПоляИнтервалВремениНапоминания();
			
			Форма[ИмяПоляНапоминатьОСобытии] = Истина;
			Форма[ИмяПоляИнтервалВремениНапоминания] = НапоминанияПользователяКлиентСервер.ПредставлениеСрокаНапоминания(Напоминание);
			Прервать;
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

#КонецОбласти
